/***************************************************************************
 *   Copyright (C) 2009 by Jason Pullara                                   *
 *   lordkat@gmail.com                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "common.h"

using namespace std;

cNetwork::cNetwork() {
	if (SDLNet_Init() < 0) {
		throw 0;
	}
	if (SDLNet_ResolveHost(&this->m_ipServerIP, NULL, 8081) < 0) {
		throw 0;
	}
	this->m_tcpServerSocket = SDLNet_TCP_Open(&this->m_ipServerIP);
	if (!this->m_tcpServerSocket) {
		throw 0;
	}
	this->m_semVecLock = SDL_CreateSemaphore(1);
	m_sdlAcceptThread = SDL_CreateThread(acceptThread, this);
	m_sdlThinkThread = SDL_CreateThread(think, this);
}

cNetwork::~cNetwork() {
	SDL_SemWait(this->m_semVecLock);
	if (this->m_tcpServerSocket)
		SDLNet_TCP_Close(this->m_tcpServerSocket);
	this->m_vecClients.clear();
	SDL_KillThread(m_sdlAcceptThread);
	SDL_KillThread(m_sdlThinkThread);
	SDL_SemPost(this->m_semVecLock);
	SDL_DestroySemaphore(this->m_semVecLock);
	SDLNet_Quit();
}

void cNetwork::sendAll(string sData) {
	SDL_SemWait(this->m_semVecLock);
	if (!this->m_vecClients.empty()) {
		for (vector<cClient *>::iterator i = this->m_vecClients.begin(); i != this->m_vecClients.end(); ++i) {
			(*i)->sendData(sData);
			(*i)->setClientState(CLIENT_STATUSBAR);
		}
	}
	SDL_SemPost(this->m_semVecLock);
}

void cNetwork::sendAllOnMap(string sNameException, string sData, unsigned long ulMap) {
	SDL_SemWait(this->m_semVecLock);
	if (!this->m_vecClients.empty()) {
		for (vector<cClient *>::iterator i = this->m_vecClients.begin(); i != this->m_vecClients.end(); ++i) {
			if (sNameException != (*i)->m_characterData.m_charData.sName && (*i)->m_characterData.m_charData.ulMap == ulMap) {
				(*i)->sendData(sData);
				(*i)->setClientState(CLIENT_STATUSBAR);
			}
		}
	}
	SDL_SemPost(this->m_semVecLock);
	return;
}

void cNetwork::sendPlayersOnMap(cClient *pClient, unsigned long ulMap) {
	string sList = "\r\nPlayer: ";
	bool bFound = false;
	SDL_SemWait(this->m_semVecLock);
	if (!this->m_vecClients.empty()) {
		for (vector<cClient *>::iterator i = this->m_vecClients.begin(); i != this->m_vecClients.end(); ++i) {
			if (pClient->m_characterData.m_charData.sName != (*i)->m_characterData.m_charData.sName && (*i)->m_characterData.m_charData.ulMap == ulMap) {
				sList += (*i)->m_characterData.m_charData.sName;
				if (bFound)
					sList += ", ";
				bFound = true;
			}
		}
	}
	sList += "\r\n";
	if (bFound)
		pClient->sendData(sList);
	SDL_SemPost(this->m_semVecLock);
	return;
}

cCharacter *cNetwork::findPlayer(string sName) {
	cCharacter *cRet = NULL;
	SDL_SemWait(this->m_semVecLock);
	if (!this->m_vecClients.empty()) {
		for (vector<cClient *>::iterator i = this->m_vecClients.begin(); i != this->m_vecClients.end(); ++i) {
			if (lowercase(sName) == lowercase((*i)->m_characterData.m_charData.sName)) {
				cRet = &(*i)->m_characterData;
				break;
			}
		}
	}
	SDL_SemPost(this->m_semVecLock);
	return cRet;
}

int cNetwork::acceptThread(void *vData) {
	TCPsocket tcpClientSocket;
	IPaddress *ipClientAddress;
	cNetwork *pNetwork = ((cNetwork *)vData);
	while (true) {
		if (tcpClientSocket = SDLNet_TCP_Accept(pNetwork->m_tcpServerSocket)) {
			if (ipClientAddress = SDLNet_TCP_GetPeerAddress(tcpClientSocket)) {
				cClient *clientTemp = new cClient(tcpClientSocket, *ipClientAddress, pNetwork);
				SDL_SemWait(pNetwork->m_semVecLock);
				pNetwork->m_vecClients.push_back(clientTemp);
				SDL_SemPost(pNetwork->m_semVecLock);
			}
			else {
				/* Something is fucked, log it with SDLNet_GetError() */
			}
		}
		SDL_Delay(1);
	}
	SDLNet_TCP_Close(pNetwork->m_tcpServerSocket);
	return 0;
}

int cNetwork::think(void *vData) {
	cNetwork *pNetwork = ((cNetwork *)vData);
	cClient *cClientTemp;
	string sTemp;
	string sCommand;
	while (true) {
		if (!pNetwork->m_vecClients.empty()) {
			for (vector<cClient *>::iterator i = pNetwork->m_vecClients.begin(); i != pNetwork->m_vecClients.end(); ++i) {
				cClientTemp = (*i);
				switch (cClientTemp->getClientState()) {
					case CLIENT_MENU:
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "clientstate_menu", "", "");
					break;
					case CLIENT_MENU_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "clientstate_menuinput", sTemp, "");
					break;
					case CLIENT_LOGIN_USERNAME:
						cClientTemp->sendData(" Username: ");
						cClientTemp->setClientState(CLIENT_LOGIN_USERNAME_INPUT);
					break;
					case CLIENT_LOGIN_USERNAME_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						if (pNetwork->findPlayer(sTemp) == NULL) {
							cClientTemp->m_characterData.m_charData = pNetwork->m_fileIO.characterLoad(sTemp);
							if (!cClientTemp->m_characterData.m_charData.sName.empty())
								cClientTemp->setClientState(CLIENT_LOGIN_PASSWORD);
							else {
								cClientTemp->sendData("Character does not exist.\r\n\r\n");
								cClientTemp->setClientState(CLIENT_MENU);
							}
						}
						else {
							cClientTemp->sendData("Character already logged in!\r\n\r\n");
							cClientTemp->setClientState(CLIENT_MENU);
						}
					break;
					case CLIENT_LOGIN_PASSWORD:
						cClientTemp->sendData(" Password: ");
						cClientTemp->setClientState(CLIENT_LOGIN_PASSWORD_INPUT);
					break;
					case CLIENT_LOGIN_PASSWORD_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						sTemp = sanitize(sTemp);
						if (sTemp.compare(cClientTemp->m_characterData.m_charData.sPassword) == 0) {
							pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_changerace", "", "");
							pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_changejob", "", "");
							pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_changesub", "", "");
							cClientTemp->m_characterData.levelChange(cClientTemp->m_characterData.m_charData.uiLevel[cClientTemp->m_characterData.m_charData.ucJob]);
							cClientTemp->m_characterData.calculateTNL();
							sTemp = "\r\n\r\n";
							sTemp += cClientTemp->m_characterData.m_charData.sName;
							sTemp += " logs in.\r\n";
							pNetwork->sendAllOnMap(cClientTemp->m_characterData.m_charData.sName, sTemp, cClientTemp->m_characterData.m_charData.ulMap);
							cClientTemp->setClientState(CLIENT_MOVEAREA);
						}
						else {
							cClientTemp->sendData("\r\nInvalid Password\r\n");
							cClientTemp->setClientState(CLIENT_MENU);
						}
					break;
					case CLIENT_CREATE_USERNAME:
						cClientTemp->sendData(" Choose a Username: ");
						cClientTemp->setClientState(CLIENT_CREATE_USERNAME_INPUT);
					break;
					case CLIENT_CREATE_USERNAME_INPUT:
						sTemp = stripBreak(sanitize(cClientTemp->queueGet()));
						if (sTemp.empty())
							break;
						if (pNetwork->m_fileIO.characterExists(stripSpace(lowercase(sTemp)))) {
							cClientTemp->sendData("Username taken.\r\n");
							cClientTemp->setClientState(CLIENT_CREATE_USERNAME);
						}
						else {
							cClientTemp->m_characterData.m_charData.sName = sTemp;
							cClientTemp->setClientState(CLIENT_CREATE_PASSWORD);
						}
					break;
					case CLIENT_CREATE_PASSWORD:
						cClientTemp->sendData(" Choose a password: ");
						cClientTemp->setClientState(CLIENT_CREATE_PASSWORD_INPUT);
					break;
					case CLIENT_CREATE_PASSWORD_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						sTemp = sanitize(sTemp);
						cClientTemp->m_characterData.m_charData.sPassword = sTemp;
						cClientTemp->m_characterData.m_charData.sTitle = "New Adventurer";
						cClientTemp->setClientState(CLIENT_CREATE_RACE);
					break;
					case CLIENT_CREATE_RACE:
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "clientstate_createrace", sTemp, "");
					break;
					case CLIENT_CREATE_RACE_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						if (sTemp.compare("1") == 0) cClientTemp->m_characterData.m_charData.ucRace = RACE_HUME;
						else if (sTemp.compare("2") == 0) cClientTemp->m_characterData.m_charData.ucRace = RACE_ELVAAN;
						else if (sTemp.compare("3") == 0) cClientTemp->m_characterData.m_charData.ucRace = RACE_TARUTARU;
						else if (sTemp.compare("4") == 0) cClientTemp->m_characterData.m_charData.ucRace = RACE_MITHRA;
						else if (sTemp.compare("5") == 0) cClientTemp->m_characterData.m_charData.ucRace = RACE_GALKA;
						else {
							cClientTemp->sendData(" Invalid race, try again.\r\n");
							cClientTemp->setClientState(CLIENT_CREATE_RACE_INPUT);
							break;
						}
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_changerace", sTemp, "");
						cClientTemp->setClientState(CLIENT_CREATE_JOB);
					break;
					case CLIENT_CREATE_JOB:
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "clientstate_createjob", sTemp, "");
					break;
					case CLIENT_CREATE_JOB_INPUT:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (sTemp.empty())
							break;
						if (sTemp.compare("1") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_WARRIOR;
						else if (sTemp.compare("2") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_MONK;
						else if (sTemp.compare("3") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_MAGE_WHITE;
						else if (sTemp.compare("4") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_MAGE_BLACK;
						else if (sTemp.compare("5") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_MAGE_RED;
						else if (sTemp.compare("6") == 0) cClientTemp->m_characterData.m_charData.ucJob = JOB_THIEF;
						else {
							cClientTemp->sendData(" Invalid job, try again.\r\n");
							cClientTemp->setClientState(CLIENT_CREATE_JOB_INPUT);
							break;
						}
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_changejob", sTemp, "");
						cClientTemp->setClientState(CLIENT_CREATE_CONFIRM);
					break;
					case CLIENT_CREATE_CONFIRM:
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "clientstate_createconfirm", sTemp, "");
					break;
					case CLIENT_CREATE_CONFIRM_INPUT:
						sTemp = cClientTemp->queueGet();
						sTemp = stripBreak(sTemp);
						sTemp = lowercase(sTemp);
						if (sTemp.empty())
							break;
						if (sTemp.compare("y") == 0) {
							cClientTemp->m_characterData.m_charData.ucSubJob = JOB_NONE;
							cClientTemp->m_characterData.changeLevel(1);
							cClientTemp->m_characterData.m_charData.ulGold = 0;
							cClientTemp->m_characterData.m_charData.ulMap = 0;
							pNetwork->m_fileIO.characterSave(cClientTemp->m_characterData.m_charData);
							sTemp = "\r\n\r\n";
							sTemp += cClientTemp->m_characterData.m_charData.sName;
							sTemp += " logs in.\r\n";
							pNetwork->sendAllOnMap(cClientTemp->m_characterData.m_charData.sName, sTemp, cClientTemp->m_characterData.m_charData.ulMap);
							cClientTemp->setClientState(CLIENT_MOVEAREA);
						}
						else if (sTemp.compare("n") == 0) {
							cClientTemp->sendData("\r\n\r\nStarting over...\r\n");
							cClientTemp->setClientState(CLIENT_CREATE_USERNAME);
						}
						else if (sTemp.compare("q") == 0) {
							cClientTemp->setClientState(CLIENT_MENU);
						}
						else {
							cClientTemp->sendData("\r\n\r\nChoices are: (Y)es, (N)o, and (Q)uit: ");
						}
					break;
					case CLIENT_CHANGE_PASSWORD:
					break;
					case CLIENT_CHANGE_PASSWORD_INPUT:
					break;
					case CLIENT_MOTD:
					break;
					case CLIENT_MOTD_INPUT:
					break;
					case CLIENT_GAME:
						sTemp = stripBreak(cClientTemp->queueGet());
						if (!sTemp.empty()) {
							int i = 0;
							if (sTemp.compare(0, 1, "/") == 0 || sTemp.compare(0, 1, ".") == 0) {
								sTemp.erase(0, 1);
								sCommand = sTemp.substr(0, strcspn(sTemp.c_str(), " \r\n\0"));
								sTemp.erase(0, sCommand.length());
								while (sTemp[0] == ' ') sTemp.erase(0, 1); // Kill any leading spaces
								pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, sCommand, sTemp, "");
							}
							else {
								while (sTemp[0] == ' ') sTemp.erase(0, 1); // Kill any leading spaces
								pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "say", sTemp, "");
							}
							pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_buildstatusbar", "", "");
						}
					break;
					case CLIENT_MOVEAREA:
						pNetwork->m_luaEngine.setData(cClientTemp, pNetwork, "");
						pNetwork->m_luaEngine.luaMapDesc();
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_buildstatusbar", "", "");
						cClientTemp->setClientState(CLIENT_GAME);
					break;
					case CLIENT_LOGOUT:
						sTemp = "\r\n";
						sTemp += cClientTemp->m_characterData.m_charData.sName;
						sTemp += " logs out.\r\n";
						pNetwork->sendAllOnMap(cClientTemp->m_characterData.m_charData.sName, sTemp, cClientTemp->m_characterData.m_charData.ulMap);
						cClientTemp->setClientState(CLIENT_QUIT);
					break;
					case CLIENT_QUIT:
						SDL_SemWait(pNetwork->m_semVecLock);
						delete (*i);
						i = pNetwork->m_vecClients.erase(i);
						SDL_SemPost(pNetwork->m_semVecLock);
					break;
					case CLIENT_ERROR:
					break;
					case CLIENT_STATUSBAR:
						pNetwork->m_luaEngine.executeCommand(cClientTemp, pNetwork, "special_buildstatusbar", "", "");
						cClientTemp->setClientState(CLIENT_GAME);
					break;
					case CLIENT_RELOADLUA:
						cClientTemp->sendData("\r\n\r\nReloading Lua...\r\n");
						pNetwork->m_luaEngine.loadLua();
						cClientTemp->sendData("\r\nDone!\r\n");
						cClientTemp->setClientState(CLIENT_STATUSBAR);
					break;
					default:
					break;
				}
				sTemp.clear();
				sCommand.clear();
				if (pNetwork->m_vecClients.empty() || i == pNetwork->m_vecClients.end())
					break;
			}
		}
		SDL_Delay(1);
	}
	return 0;
}
